//
//  APIPlugins.swift
//  Casecord
//
//  Created by baozi on 2024/1/3.
//

import Foundation
import Moya
import Alamofire
import CryptoSwift

/// API 业务插件
protocol APIPlugin {
    func didStart(api: TargetType, _ behaviors: Set<APIRequestBehavior>?)
    func didEnd(api: TargetType, _ behaviors: Set<APIRequestBehavior>?, _ response: APIResult?, _ error: APIError?)
}
extension APIPlugin {
    func didStart(api: TargetType, _ behaviors: Set<APIRequestBehavior>?) {}
    func didEnd(api: TargetType, _ behaviors: Set<APIRequestBehavior>?, _ response: APIResult?, _ error: APIError?) {}
}

class APIRequestEnCryptPlugin: PluginType {
    func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
        guard let api = target as? APITargetType else {
            return request
        }
        var request = request
        request.timeoutInterval = api.timeout
        var headers = HTTPHeaders()
        headers.add(name: "Accept-Encoding", value: "identity")
        headers.add(name: "Content-Type", value: "application/json;charset=UTF-8")
        headers.add(name: "Content-Language", value: "en")
        headers.add(name: "content-sign", value: Env.current.httpScheme)
        headers.add(name: "content-status", value: "1")
        if let userId = UserService.user?.userId {
            headers.add(name: "userId", value: "\(userId)")
        }
        headers.add(name: "Accept", value: "application/json;charset=UTF-8")
        
        if api.decrypt{
            headers.add(name: "need-decrypt", value: "1")
        }
        if api.encrypt{
            headers.add(name: "need-encrypt", value: "1")
        }
        if (api.sign) {
            //                PPNetLog("-----------加密开始--------")
            headers.add(name: "need-sign", value: "1")
            let timeStr = "\(ServerTimePlugin.millisecond)"
            //                let timeStr = "1679555476422"
            headers.add(name: "timestamp", value: timeStr)
            
            var originStr = ""
            if api.method == .get || api.method == .delete || api.method == .head {
                if let paramStr = request.url?.query,
                   paramStr.count > 0 {
                    originStr.append(paramStr)
                    originStr.append("&")
                }
            } else {
                if let jsonStr = api.paramtersString,
                   jsonStr.count > 0
                {
                    originStr.append(jsonStr)
                    originStr.append("&")
                }
            }
            originStr.append("contentSign=\(Env.current.httpScheme)&timestamp=\(timeStr)")
            originStr.append(Const.aesStrKey())
            //                PPLog("加密之前====\(originStr)====aeskey===\(Const.aesStrKey())")
            
            let encryptStr = originStr.sha256().lowercased()
            //                PPNetLog("加密前: \(originStr)")
            //                PPNetLog("加密后: \(encryptStr)")
            headers.add(name: "sign", value: encryptStr)
            //                PPNetLog("-----------加密结束--------")
            
            if let token = UserService.shared.current?.user?.token,
               !token.isEmpty {
                headers.add(name: "token", value: token)
            }
        }
        request.headers = headers
        return request
    }
    
    func process(_ result: Result<Response, MoyaError>, target: TargetType) -> Result<Response, MoyaError> {
        guard let api = target as? APITargetType else {
            return result
        }
        switch result {
        case let .success(response):
            if api.thirdApi || api.decrypt == false {
                return result
            } else {
                if let dataStr = String(data: response.data, encoding: .utf8),
                   let desString = CryptTool.aesDecrypt(value: dataStr)
                {
                    let removeSchemeDesString = _removeSchemeKey(jsonString: desString)
//                    PPNetLog(removeSchemeDesString)
                    if let data = removeSchemeDesString.data(using: .utf8) {
                        let res = Response(statusCode: response.statusCode, data: data)
                        return .success(res)
                    }
                }
                return result
            }
        default:
            return result
        }
        
    }
    
    private func _removeSchemeKey(jsonString: String) -> String {
        do {
            let pattern = "(\"\(Env.current.httpScheme))([a-zA-Z0-9_]+)(\":)"
            let regex = try NSRegularExpression(pattern: pattern, options: [])
            let range = NSRange(location: 0, length: jsonString.utf16.count)
            
            var mutableString = jsonString
            
            let matches = regex.matches(in: jsonString, options: [], range: range)
            for match in matches.reversed() {
                let oldKeyRange = Range(match.range(at: 0), in: mutableString)!
                let keyRange = Range(match.range(at: 2), in: mutableString)!
                let newKey = mutableString[keyRange].prefix(1).lowercased() + mutableString[keyRange].dropFirst()
                mutableString = mutableString.replacingCharacters(in: oldKeyRange, with: "\"\(newKey)\":")
            }
            return mutableString
        } catch {
            return ""
        }
    }
}


/// 业务层插件，记录、矫正本地时间和服务端时间的偏差
final class ServerTimePlugin: APIPlugin {
    
    public static let shared = ServerTimePlugin()
    private static let timeOffsetKey: KVStoreKey = KVStoreKey<Int64>(rawValue: "ServerTime.offset")
    private var timeOffset: Int64 = 0
    
    private init() {
        timeOffset = KVStore[ServerTimePlugin.timeOffsetKey] ?? 0
        UIApplication.willTerminateNotification.rx().subscribe(onNext: { _ in
            KVStore[ServerTimePlugin.timeOffsetKey] = Self.shared.timeOffset
        }).disposed(by: globalDisposeBag)
    }
    
    public static var millisecond: Int64 {
        get {
            Int64(Date().timeIntervalSince1970 * 1000) + shared.timeOffset
        }
        set {
            guard newValue > 0 else { return }
            let localTime: Int64 = Int64(Date().timeIntervalSince1970 * 1000)
            shared.timeOffset = newValue - localTime
        }
    }
    
    public static var date: Date {
        Date(timeIntervalSinceNow: Double(shared.timeOffset) / 1000)
    }
    
    func didEnd(api: TargetType, _ behaviors: Set<APIRequestBehavior>?, _ response: APIResult?, _ error: APIError?) {
        if let response = response, let millisecond = response.timestamp {
            Self.millisecond = millisecond
        } else if error == APIError.err_timeoffset_1000006, let offset = error?.data as? Int64 {
            Self.millisecond = offset
        } else if error == APIError.moyaError, let moyaErr = error?.data as? MoyaError,
                  case let .underlying(_, moyaResponse) = moyaErr,
                  let statusCode = moyaResponse?.statusCode,
                  statusCode == 408 { // 网络请求超时
            Self.millisecond = Int64(Date().timeIntervalSince1970 * 1000)
        }
    }
}

/// 网络请求打印插件
class APINetworkLoggerPlugin: PluginType {
    private let dateFormatter = DateFormatter().then {
        $0.dateFormat = "[dd/MM/yyyy HH:mm:ss.SSS]"
        $0.locale = Locale(identifier: "en_US_POSIX")
    }
    var date: String {
        return dateFormatter.string(from: Date())
    }
    
    /// 请求发送前
    func willSend(_ request: RequestType, target: TargetType) {
        guard let api = target as? APITargetType else {
            return
        }
        QueueCommon.async {
            var output: [String] = []
            // Path method
            output += ["请求地址:\(api.baseURL)请求路径: \(api.path) Method: \(api.method.rawValue)\n"]
            // 全局header
            output += ["请求Header: \(request.request?.headers.dictionary.jsonString ?? "")\n"]
            // params
            output += ["请求param:\n", "\(api.params?.jsonString ?? "")"]
            // 全部参数
            _print(output, prefix: "🌐🌐🌐")
        }
    }
    
    func process(_ result: Result<Response, MoyaError>, target: TargetType) -> Result<Response, MoyaError> {
        guard let api = target as? APITargetType else {
            return result
        }
        
        QueueCommon.async {
            var output: [String] = []
            // Path method
            output += ["请求路径: \(api.path) Method: \(api.method.rawValue)\n"]
            
            switch result {
            case let .success(response):
                do {
                    let dataStr = String(data: response.data, encoding: .utf8) ?? ""
                    let m = try JSONDecoder().decode(APIResult.self, from: dataStr.data(using: .utf8)!)
                    let str = m.toCodableString(prettyPrint: true)
                    output += [str ?? ""]
                    _print(output, prefix: "🌐🌐🌐")
                } catch {
                    
                }
            case let .failure(error):
                output += [error.localizedDescription]
                _print(output, prefix: "💣💣💣")
            }
        }
        return result
    }
}

private func _print(_ items: [Any?], prefix: String, separator: String = " ", terminator: String = "\n") {
#if DEBUG || TEST
    Swift.print(prefix, terminator: separator)
    
    var idx = items.startIndex
    let endIdx = items.endIndex
    repeat {
        let item = items[idx]
        if item is Error {
            Swift.print(item.debugDescription, separator: separator, terminator: idx == (endIdx - 1) ? terminator : separator)
        } else {
            let tempIetm = item ?? "\(type(of: item)): nil"
            Swift.print(tempIetm, separator: separator, terminator: idx == (endIdx - 1) ? terminator : separator)
        }
        idx += 1
    } while idx < endIdx
#endif
}

/// 余额插件, 实时更新用户余额
final class APIPluginBalance: APIPlugin {
    static let shared: APIPluginBalance = APIPluginBalance()
    private init() {}
    
    @MainActor func didEnd(api: TargetType, _ behaviors: Set<APIRequestBehavior>?, _ response: APIResult?, _ error: APIError?) {
        if let res = response,
           let _ = res.ext,
           let ext = res.unwrap(type: APIExt.self, key: .ext) {
            if let coin = ext.rechargeCoin {
                UserService.shared.current?.ext?.rechargeCoin = coin
            }
        }
    }
}
